
#![allow(unused_variables)]
#![allow(dead_code)]

use std::ffi::CString;
use std::mem;

use once_cell::sync::Lazy;
use uuid::Uuid;
use windows::Win32::Foundation::{DUPLICATE_CLOSE_SOURCE, DUPLICATE_HANDLE_OPTIONS, DUPLICATE_SAME_ACCESS, HANDLE};
use windows::core::Error as WinError;


use crate::win32_trace;

use super::utils;

use super::enums::AtomicMode;
use super::win_handle::WinHandle;





// lazy_static! {
//     static ref CURRENT_PROCESS_ID: u32 = unsafe { windows::Win32::System::Threading::GetCurrentProcessId() };
//     static ref CURRENT_PROCESS_HANDLE: WinHandle = WinHandle::new(unsafe { windows::Win32::System::Threading::GetCurrentProcess() });
// }

pub(super) static CURRENT_PROCESS_ID: Lazy<u32> = Lazy::new(|| {
    unsafe { windows::Win32::System::Threading::GetCurrentProcessId() }
});
pub(super) static CURRENT_PROCESS_HANDLE: Lazy<WinHandle> = Lazy::new(|| {
    WinHandle::new(unsafe { windows::Win32::System::Threading::GetCurrentProcess() })
});

// pub(super) static DEBUG_TRACE_ENABLED: Lazy<bool> = Lazy::new(|| {
//     env::var_os("IPC_CHANNEL_WIN_DEBUG_TRACE").is_some()
// });

// lazy_static! {
//     static ref WINDOWS_APP_MODULE_NAME_CSTRING: CString =
//         CString::new(crate::platform::os::utils::WINDOWS_APP_MODULE_NAME).unwrap();
//     static ref COMPARE_OBJECT_HANDLES_FUNCTION_NAME_CSTRING: CString =
//         CString::new(crate::platform::os::utils::COMPARE_OBJECT_HANDLES_FUNCTION_NAME).unwrap();
// }
pub(super) static WINDOWS_APP_MODULE_NAME_CSTRING: Lazy<CString> = Lazy::new(|| {
    CString::new( utils::WINDOWS_APP_MODULE_NAME).unwrap()
});


pub(super) static COMPARE_OBJECT_HANDLES_FUNCTION_NAME_CSTRING: Lazy<CString> = Lazy::new(|| {
    CString::new( utils::COMPARE_OBJECT_HANDLES_FUNCTION_NAME).unwrap()
});





/// When we create the pipe, how big of a write buffer do we specify?
///
/// This is reserved in the nonpaged pool.  The fragment size is the
/// max we can write to the pipe without fragmentation, and the
/// buffer size is what we tell the pipe it is, so we have room
/// for out of band data etc.
pub(super) const MAX_FRAGMENT_SIZE: usize = 64 * 1024;

/// Size of the pipe's write buffer, with excess room for the header.
pub(super) const PIPE_BUFFER_SIZE: usize = MAX_FRAGMENT_SIZE + 4 * 1024;



pub(super) fn make_pipe_id() -> Uuid {
    Uuid::new_v4()
}

pub(super) fn make_pipe_name(pipe_id: &Uuid) -> CString {
    CString::new(format!("\\\\.\\pipe\\rust-ipc-{}", pipe_id.to_string())).unwrap()
}

/// Duplicate a given handle from this process to the target one, passing the
/// given flags to DuplicateHandle.
///
/// Unlike win32 DuplicateHandle, this will preserve INVALID_HANDLE_VALUE (which is
/// also the pseudohandle for the current process).
pub(super) fn dup_handle_to_process_with_flags(
    handle: &WinHandle,
    other_process: &WinHandle,
    flags: DUPLICATE_HANDLE_OPTIONS,
) -> Result<WinHandle, WinError> {
    if !handle.is_valid() {
        return Ok(WinHandle::invalid());
    }

    unsafe {
        let mut new_handle: HANDLE = windows::Win32::Foundation::INVALID_HANDLE_VALUE;
        let ok = windows::Win32::Foundation::DuplicateHandle(
            CURRENT_PROCESS_HANDLE.as_raw(),
            handle.as_raw(),
            other_process.as_raw(),
            &mut new_handle,
            0,
            false,
            flags,
        );
        if ok == false {
            Err(WinError::from_win32())
        } else {
            Ok(WinHandle::new(new_handle))
        }
    }
}

/// Duplicate a handle in the current process.
pub(super) fn dup_handle(handle: &WinHandle) -> Result<WinHandle, WinError> {
    dup_handle_to_process(handle, &WinHandle::new(CURRENT_PROCESS_HANDLE.as_raw()))
}

/// Duplicate a handle to the target process.
pub(super) fn dup_handle_to_process(
    handle: &WinHandle,
    other_process: &WinHandle,
) -> Result<WinHandle, WinError> {
    dup_handle_to_process_with_flags(handle, other_process, DUPLICATE_SAME_ACCESS)
}

/// Duplicate a handle to the target process, closing the source handle.
pub(super) fn move_handle_to_process(
    handle: WinHandle,
    other_process: &WinHandle,
) -> Result<WinHandle, WinError> {
    let result = dup_handle_to_process_with_flags(
        &handle,
        other_process,
        DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS,
    );
    // Since the handle was moved to another process, the original is no longer valid;
    // so we probably shouldn't try to close it explicitly?
    mem::forget(handle);
    result
}


pub(super) const WINDOWS_APP_MODULE_NAME: &'static str = "api-ms-win-core-handle-l1-1-0";
pub(super) const COMPARE_OBJECT_HANDLES_FUNCTION_NAME: &'static str = "CompareObjectHandles";


/// Write data to a handle.
///
/// In `Atomic` mode, this panics if the data can't be written in a single system call.
pub(super) fn write_buf(handle: &WinHandle, bytes: &[u8], atomic: AtomicMode) -> Result<(), WinError> {
    let total = bytes.len();
    if total == 0 {
        return Ok(());
    }

    let mut written = 0;
    while written < total {
        let mut sz: u32 = 0;
        let bytes_to_write = &bytes[written..];
        unsafe {
            if windows::Win32::Storage::FileSystem::WriteFile(handle.as_raw(), Some(bytes_to_write), Some(&mut sz), None) == false {
                return Err(WinError::from_win32());
            }
        }
        written += sz as usize;
        match atomic {
            AtomicMode::Atomic => {
                if written != total {
                    panic!("Windows IPC write_buf expected to write full buffer, but only wrote partial (wrote {} out of {} bytes)", written, total);
                }
            },
            AtomicMode::Nonatomic => {
                win32_trace!(
                    "[c {:?}] ... wrote {} bytes, total {}/{} err {}",
                    handle.as_raw(),
                    sz,
                    written,
                    total,
                    WinError::from_win32()
                );
            },
        }
    }

    Ok(())
}